1. Abstract factory
1.1. Introduction
This is how it is summarized in the GoF book , remark that the word "interface" is used in a more general interpretation (GoF is not using that as a Java interface), in Java it can be interpreted as an interface or abstract base class.
Provide an interface for creating families of related or dependent objects without specifying their concrete classes.
The Abstract Factory pattern decouples - as in loosely coupled - the dependent Client from the concrete classes that implement some functionality.
The pattern does not describe how the concrete factory is obtained. At the moment of writing, injecting the concrete factory using Spring , is widely regarded as a best practice. This approach is explained in more detail later on.
This example is about some ordering system that will communicate with supplier systems in different ways. By calling a SOAP webservice or sending an e-mail to an agreed e-mail address. Both implementations need a distinct set of implementing classes. Each of these set of concrete classes is created by a distinct, concrete subclass of AbstractFactory .
I even considered generating sound file with FreeTTS - an implementation of JSAPI - and deliver that to a telephony service such as Asterisk. But that would lead too far.
1.2. Code
The basic things to consider are the Client class and the orderservice-config.xml Spring configuration file. The rest is available for completeness.
1.2.1. Client
The Client class here, is a very simplistic implementation that serves to illustrate how:
- A concrete factory instance is obtained, without EmailFactory or SOAPFactory being mentioned anywhere.
- How the client would place an order.
The advantage is that we can add and remove new sets of implementation (factory, service and message), change the concrete implementation we want for a particular supplier with no impact on the Client code.
These few lines of code will instantiate: 1 Client, 2 SOAPFactory, 1 EmailFactory and connect them all together as desired.
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("be/ooxs/examples/designpatterns/abstractfactory/orderservice-config.xml"); Object o = context.getBean("client", Client.class);
So when processing an order later on, this will happen:
AbstractFactory factory = getFactory(order.getSupplierName()); //"supplier2", factory is a SOAPFactory instance OrderService service = factory.createService(); // service is a SOAPService OrderMessage message = factory.createMessage(null); // message is a SOAPMessage service.process(message);
And here is the complete class:
package be.ooxs.examples.designpatterns.abstractfactory; import java.util.Map; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Client { private Map<String, AbstractFactory> factories; public void processOrder(Order order) { AbstractFactory factory = getFactory(order.getSupplierName()); OrderService service = factory.createService(); OrderMessage message = factory.createMessage(null); service.process(message); } protected AbstractFactory getFactory(String supplierName) { return factories.get(supplierName); } public Map<String, AbstractFactory> getFactories() { return factories; } public void setFactories(Map<String, AbstractFactory> factories) { this.factories = factories; } public static void main(String[] args) { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("be/ooxs/examples/designpatterns/abstractfactory/orderservice-config.xml"); Object o = context.getBean("client", Client.class); Client client = (Client) o; Order order = new Order(); order.setDescription("Felt Pen Black"); order.setProductCode("AA001"); order.setQuantity(5); order.setSupplierName("supplier2"); System.out.println(client.getFactory(order.getSupplierName()).getClass()); // client.processOrder(order); } }
Apparently, the most difficult part in understanding how AbstractFactory can help us, is seeing how a concrete factory is selected.
A possible example is using Spring IOC to inject these factories into Client . By the way, Client is just a placeholder for any code that uses AbstractFactory or any other design pattern
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.0.xsd" > <bean id="supplier1" class="be.ooxs.examples.designpatterns.abstractfactory.SOAPFactory"> <property name="url" value="http://supplier1/axis/orderservice" /> <property name="nameSpace" value="http://ns.ooxs.be/soap/orderservice" /> </bean> <bean id="supplier2" class="be.ooxs.examples.designpatterns.abstractfactory.SOAPFactory"> <property name="url" value="http://supplier2/orderservice" /> <property name="nameSpace" value="http://ns.ooxs.be/soap/orderservice" /> </bean> <bean id="supplier3" class="be.ooxs.examples.designpatterns.abstractfactory.EmailFactory"> <property name="fromAddress" value="aa@example" /> <property name="toAddress" value="bb@supplier3" /> <property name="ccAddress" value="followup@example" /> <property name="smtpHostName" value="smtp.host.name" /> </bean> <bean id="client" class="be.ooxs.examples.designpatterns.abstractfactory.Client"> <!-- This is the actual magic Here supplier names are mapped to an instance of AbstractFactory --> <property name="factories"> <map key-type="java.lang.String" value-type="be.ooxs.examples.designpatterns.abstractfactory.AbstractFactory"> <entry> <key><value>supplier1</value></key> <ref bean="supplier1" /> </entry> <entry> <key><value>supplier2</value></key> <ref bean="supplier2" /> </entry> <entry> <key><value>supplier3</value></key> <ref bean="supplier3" /> </entry> </map> </property> </bean> </beans>
1.2.2. Abstractions
Here are the three abstract and utility classes that are known to the Client
public abstract class AbstractFactory { public abstract OrderMessage createMessage(Order order); public abstract OrderService createService(); }
Order is a simple class used elsewhere in the ordering system. In a real world system it would probably refer to a Supplier and Product class instead of Strings, but hey, let's keep it simple here.
public class Order { private String supplierName; private String productCode; private String description; private int quantity; // declaration of getters and setters ... }
The message implementation:
import java.util.Date; public class OrderMessage { private Date sentTime; private String resultText; // declaration of getters and setters ... }
The service implementation:
public abstract class OrderService { public abstract void process(OrderMessage message); }
1.2.3. SOAP Implementation
Here we have the three classes that provide a SOAP implementation.
In this case we make SOAPService an inner class of the SOAPFactory , it is just one approach for accessing the url and nameSpace parameters
import java.net.MalformedURLException; import java.rmi.RemoteException; import java.util.Date; import javax.xml.namespace.QName; import javax.xml.rpc.ServiceException; import org.apache.axis.client.Call; import org.apache.axis.client.Service; public class SOAPFactory extends AbstractFactory { private String url = "http://supplier.example.com/axis/orderservice"; private String nameSpace = "http://ns.ooxs.be/soap/orderservice"; @Override public OrderService createService() { return new SOAPService(); } @Override public OrderMessage createMessage(Order order) { return new SOAPMessage(order); } // declaration of getters and setters ... /** * The concrete SOAP based OrderService */ class SOAPService extends OrderService { @Override public void process(OrderMessage message) { process((SOAPMessage) message); } public void process(SOAPMessage message) { try { Service service = new Service(); Call call = (Call) service.createCall(); String operationName = "orderProduct"; Object[] arguments = new Object[] { message.getQuantity(), message.getProductCode() }; call.setTargetEndpointAddress(new java.net.URL(url)); call.setOperationName(new QName(nameSpace, operationName)); Object returnValue = call.invoke(arguments); message.setSentTime(new Date()); message.setResultText(String.valueOf(returnValue)); } catch (MalformedURLException exception) { exception.printStackTrace(); message.setResultText("Service URL not correct (config"); } catch (RemoteException exception) { exception.printStackTrace(); message.setResultText("Unable to use service"); } catch (ServiceException exception) { exception.printStackTrace(); message.setResultText("Something went wrong while using the service"); } } } }
public class SOAPMessage extends OrderMessage { private Order order; SOAPMessage(Order order) { super(); this.order = order; } public String getProductCode() { return order.getProductCode(); } public int getQuantity() { return order.getQuantity(); } }
1.2.4. Email Implementation
Here we have the three classes that provide a more primitive e-mail based implementation.
In this case we do not use an inner class, we copy the parameters when instantiating the EmailService .
public class EmailFactory extends AbstractFactory { private String smtpHostName; private String fromAddress; private String ccAddress; private String toAddress; @Override public OrderService createService() { return new EmailService(smtpHostName, fromAddress, ccAddress, toAddress); } @Override public OrderMessage createMessage(Order order) { return new EmailMessage(order); } // declaration of getters and setters ... }
The message implementation:
public class EmailMessage extends OrderMessage { private Order order; EmailMessage(Order order) { this.order = order; } public String getHtmlContent() { StringBuilder b = new StringBuilder(); b.append("<html><head><title>Order for ").append(order.getDescription()); b.append("<title><head><body>"); b.append("<p>Please send us:</p>"); b.append("<p>").append(order.getQuantity()).append("x ").append(order.getDescription()).append("</p>"); b.append("<p>Thank you</p>"); b.append("</body></html>"); return b.toString(); } public String getSubject() { return "Order for " + order.getDescription(); } }
The service implementation:
import java.util.Date; public class EmailService extends OrderService { private Properties mailProperties; private String fromAddress; private String ccAddress; private String toAddress; EmailService(String smtpHostName, String fromAddress, String ccAddress, String toAddress) { super(); mailProperties = new Properties(); mailProperties.setProperty("mail.host", smtpHostName); this.fromAddress = fromAddress; this.ccAddress = ccAddress; this.toAddress = toAddress; } @Override public void process(OrderMessage message) { send((EmailMessage) message); } public void send(EmailMessage mail) { try { MimeMessage message = createMimeMessage(mail); Transport.send(message); mail.setSentTime(new Date()); } catch (MessagingException exception) { exception.printStackTrace(); mail.setResultText(exception.getMessage()); } } protected MimeMessage createMimeMessage(EmailMessage mail) throws MessagingException { MimeMultipart multiPart = new MimeMultipart(); BodyPart htmlPart = new MimeBodyPart(); htmlPart.setContent(mail.getHtmlContent(), "text/html"); multiPart.addBodyPart(htmlPart); MimeMessage message = new MimeMessage(Session.getInstance(mailProperties, null)); message.setSubject(mail.getSubject()); message.setRecipients(Message.RecipientType.TO, toAddress); message.setRecipients(Message.RecipientType.CC, ccAddress); message.setFrom(new InternetAddress(fromAddress)); message.setContent(multiPart); return message; } }